Skip to content

kernel/vtpm: Support vtpm invocation using an external state file#1124

Draft
armenon-rh wants to merge 6 commits into
coconut-svsm:mainfrom
armenon-rh:pr4-vtpm-persistence
Draft

kernel/vtpm: Support vtpm invocation using an external state file#1124
armenon-rh wants to merge 6 commits into
coconut-svsm:mainfrom
armenon-rh:pr4-vtpm-persistence

Conversation

@armenon-rh

@armenon-rh armenon-rh commented Jun 16, 2026

Copy link
Copy Markdown

Add vTPM state file as a parameter to vtpm_init() allowing it to initialize the vTPM with an external state file.

In order to create an external vTPM state file image, that can be attached to svsm as a virtio-blk device, use vtpm-to-svsm-blk

This PR depends on: #1123. Please review only the commits 6cfe74e, 9af3f2c

@stefano-garzarella stefano-garzarella self-assigned this Jun 16, 2026
@msft-jlange msft-jlange added the needs-rebase The PR needs to be rebased to the latest upstream branch label Jul 1, 2026
Currently the attestation flow only works against the mock attestation
server. Against the production-ready Trustee KBS (CoCo), attestation
fails immediately because the cryptographic hashes do not match.

The RCAR (Request-Challenge-Attestation-Response) handshake forms the
basis of secret retrieval from KBS. This has 2 parts, negotiation and
attestation. Negotiation phase typically only allows the client to get a
nonce from the KBS, and this nonce will be used in the attestation
request in the subsequent step. For SVSM, the client is SVSM + aproxy.

The current negotiation request:
  SVSM sends only version and tee to aproxy. SVSM public key is not sent.
The current negotiation response:
  aproxy fetches the challenge nonce from KBS and send it back to SVSM.
The current hashing in svsm:
  SVSM does the hashing locally in the kernel by concatenating the raw
bytes of the nonce and the pub_key coordinates, hashing them with
SHA-512.

Report data = sha512(nonce + pub_key_x + pub_key_y).
This is signed by the AMD processor.

The real Trustee KBS does not expect a raw concatenation of bytes hashed
with SHA-512. Instead, the KBS verifier expects the runtime data to be a
JWS-formatted JSON object containing specific parameters, hashed with
SHA-384, see below.

{
  "nonce": kbs-nonce,
  "additional-evidence": "",
  "tee-pubkey": {
    "kty": "EC",
    "crv": "P-521",
    "x": "..."
    "y": "..."
  }
}

Although the hashing and serializing can be done in SVSM, it is not wise
to add/implement the JSON serializer in the kernel. We would also
require a base64url encoder. Besides, if SVSM constructs the
KBS-specific JSON, SVSM becomes tightly coupled to the specific API
implementation of a single KBS (Trustee).

For those reasons, the job of serialising and hashing should be done by
the aproxy. Because the baremetal SVSM kernel cannot do complex JSON
formatting, serialization, JWS mapping and base64 encoding, SVSM could
not compute this JSON-based hash. As a result the hash SVSM signed in the report
never matches the hash that KBS expected, and the attestation failed.

This commit allows aproxy to have the public key of svsm ready to use
during the negotiation phase itself. By sending SVSM's public key during
negotiation, aproxy can compute the SHA-384 of the constructed JSON first,
and pass it back to SVSM. SVSM can pass that hash to the AMD CPU to be
hardware-signed.

SVSM              APROXY        Trustee KBS
  |                 |                |
  |                 |                |
  | 1. Negotiation  |                |
  +---------------->|                |
  |    Request      |                |
  |(tee, ver, pk)   |2. GET/auth     |
  |                 +--------------->|
  |                 | 3. Returns     |
  |                 |<---------------+
  |                 | challenge nonce|
  |                 |                |
  |                 |                |
  |                 |                |
  |                 |4. Precomputes  |
  |                 |   KBS hashing. |
  |                 |a. Constructs runtime_data JSON
  |                 |  (with nonce + SVSM key)
  |                 |b. Computes sha-384 of this JSON
  |                 |   This is called challenge hash
  |                 |                |
  |                 |                |
  |                 |                |
  |  5. Negotiation |                |
  |<----------------+                |
  |     Response    |                |
  |                 |                |
  |                 |                |
  |                 |                |
  |6. Requests Hardware Report       |
  |   Passes Challenge hash as       |
  |   REPORT_DATA. AMD CPU signs     |
  |   Challenge Hash|                |
  |                 |                |
  |                 |                |
  |                 |                |
  |                 |                |
  |  7. Attestation |                |
  +---------------->|                |
  |     Request     |                |
  | Sends TEE report|                |
  | + SVSM pubkey   |                |
  |                 | 8. POST/attest |
  |                 +--------------->|
  |                 | -Sends TEE rep |
  |                 | -Sends runtime_|
  |                 |  data JSON with|
  |                 | (nonce + pubkey)
  |                 |                |
  |                 |                |
  |                 |                | 9.Verification at KBS
  |                 |                |  a. Compute sha384 of runtime
  |                 |                |     data (covers nonce+pubkey)
  |                 |                |  b. Reads REPORT_DATA from TEE
  |                 |                |     Report. Contains (challenge Hash)
  |                 |                |  c. Compares both. Attestation
  |                 |                |     accepted. Issue attestation token.
  v                 v                v

A subsequent commit will move the JSON construction and hashing
responsibility out of the kernel, and put it into the aproxy.

Signed-off-by: Arun Menon <armenon@redhat.com>
Running SVSM against a trustee KBS service to verify the
attestation report and get the attestation token back fails with the
error "Deserialize SNP Evidence failed" [1]

On further inspection, here are a few problems identified with the
current attestation flow:

1. The trustee KBS service depends on the virtee sev crate
   AttestationReport structure [2] and it does not match with the
   structure of AttestationReport that SVSM sends to aproxy.
   As a result the deserialization fails.
2. The additional_evidence is not passed in the report at all.
3. Runtime data is hashed using sha512 whereas KBS service expects to
   use Sha384 hash function.
4. nonce should be stored from the negotiation response, so that it can
   be sent back in the attestation request.

In this commit, we add the official 'sev' crate (from virtee, with snp
and serde features enabled) to aproxy's dependencies, and implement a
decoupled KBS backend in tools/aproxy. [3]

The aproxy KBS backend parses the raw report bytes received from SVSM
using sev::firmware::guest::AttestationReport. This completely decouples
the SVSM kernel from KBS serialization format requirements.
The standard parser utilized by the KBS evidence verifier is used in the
aproxy, resolving previous deserialization failures.

aproxy backend now constructs the complete, KBS-compliant runtime_data
JSON object (which contains the nonce and the tee-pubkey formatted
with JWS parameters like alg: 'ECDH-ES+A256KW' and crv: 'P-521').
aproxy serializes this entire JSON object, computes its SHA-384 hash,
and returns this combined hash to SVSM as the NegotiationResponse challenge.
SVSM then simply hashes this single challenge into its SEV-SNP report.

Update the hash function in kernel/src/attest.rs to directly copy the
48 byte challenge into a zero padded 64 byte array. Hashing is actually
performed in the aproxy and the hash function used is Sha384

Casting SNP report bytes into sev crate's AttestationReport structure
via read_unaligned causes undefined behaviour due to optimized Option
enums. Replace unsafe cast with sev::parser::Decoder to safely decode
the report byte-by-byte into the correct memory layout.

Adding resource_id: Option<String> to KbsProtocol prevents the Protocol
enum from implementing Copy. Therefore pattern matching on self.protocol
directly inside HttpClient method now borrows self, and that conflicts
with subsequently passing self as mutable reference to the backend method
calls like negotiation and attestation. Use idiomatic mem::replace pattern
to avoid this.

Since sev crate introduces std, make clippy --all-features fails.
Invoking clippy with CARGO_HACK=1 succeeds.
make clippy CARGO_HACK=1

[1] https://github.com/confidential-containers/trustee/blob/main/deps/verifier/src/snp/mod.rs#L351
[2] https://github.com/confidential-containers/trustee/blob/main/deps/verifier/Cargo.toml#L96
[3] https://docs.rs/crate/sev/8.0.0/source/src/firmware/guest/types/snp.rs#255

Signed-off-by: Arun Menon <armenon@redhat.com>
The request from aproxy to KBS, post a successful attestation, could
be a secret resource stored on the storage backend or a simple call to
the numerous plugins that KBS offers.

SVSM intends to use the wrap-key endpoint of KBS to retrieve the
unwrapped key to decrypt the vTPM state file. The SecretRequest enum
captures these two variants, and can be extended in future.
This is added to AttestationRequest from SVSM and the aproxy can hit
the KBS wrap-key endpoint immediately post attestation.

Signed-off-by: Arun Menon <armenon@redhat.com>
Introduce the fetch_secret helper function in the KBS attestation
protocol backend to handle the newly introduced SecretRequest flow.

This function allows the user to fetch the secret based on the type of
the kbs backend it is stored in.

Signed-off-by: Arun Menon <armenon@redhat.com>
SVSM's vTPM could not receive external persistent state on init, nor
did it store the KBS key for future state encryption/decryption.

Update vtpm_init and TCG TPM backend to accept an optional state
buffer and store the KBS secret key in the TcgTpm struct. This stored
key will be used for future state decryption on startup.

Even though state is passed as a buffer to the vtpm_init() function, it
does not come in effect yet, because the tpm reference implementation
does not copy platParameter into s_NV buffer in _plat__NVEnable()
function.

Signed-off-by: Arun Menon <armenon@redhat.com>
Update SVSM guest to read the first sector from the virtio block device
to extract the 256-byte wrapped key (wrapped with HSM's public key) from
the "SVSMvTPM" header at offset 64.

Modify the SVSM attestation driver interface to accept and send this wrap
key payload directly to aproxy within the attestation request.

Update vTPM state loading process to start reading the encrypted state
payload from offset 320.

If the guest's block device does not contain a valid SVSMvTPM magic
header or wrapped key, abort the initialization to prevent silent boot
failures. This is a PoC implementation commit, and is intended to be
replaced with the cocoon-fs implementation at a later stage.

Signed-off-by: Arun Menon <armenon@redhat.com>
@armenon-rh armenon-rh force-pushed the pr4-vtpm-persistence branch from bfeb3d1 to 9af3f2c Compare July 1, 2026 21:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-rebase The PR needs to be rebased to the latest upstream branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants